package com.wechat.websocket.netty;

import com.wechat.entity.dto.TokenUserInfoDto;
import com.wechat.redis.RedisComponent;
import com.wechat.utils.StringTools;
import com.wechat.websocket.ChannelContextUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Description ws 业务处理：处理客户端的发送的websocket信息
 * TextWebSocketFrame指定处理器只处理ws的文本帧，netty框架会根据事件类型自动调用相关方法
 * 先建立连接触发channelActive方法，之后触发用户事件触发器，处理握手成功事件，将用户id与channel绑定
 * 然后接收到完整的文本信息，调用channelRead方法处理
 * 期间持续检测心跳，如果超时则关闭连接
 */

/**
 * 设置通道共享
 */
@ChannelHandler.Sharable//可以在多个pipeline中使用同一个channelHandler实例（ws可以同时处理多个客户端连接，避免重复创建实例）
@Component("handlerWebSocket")
public class HandlerWebSocket extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private static final Logger logger = LoggerFactory.getLogger(HandlerWebSocket.class);

    @Resource
    private ChannelContextUtils channelContextUtils;

    @Resource
    private RedisComponent redisComponent;

    /**
     * 读取数据，处理 服务端 接收到的 客户端 通过websocket发送文本信息 后调用
     * @param ctx
     * @param textWebSocketFrame
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
        Channel channel = ctx.channel();
        //channel.attr的作用是用于Channel上存储自定义属性，属性值可以通过AttributeKey来获取或设置
        String userId = (String) channel.attr(AttributeKey.valueOf(channel.id().toString())).get();//获取userId
        logger.info("收到userId{}的消息：{}",userId,textWebSocketFrame.text());
        //收到客户端消息，保存心跳
        redisComponent.saveUserHeartBeat(userId);
    }

    /**
     *  当客户端 TCP 连接成功建立且 WebSocket 握手完成后调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("通道就绪,有新的连接加入：{}",ctx.channel().id().toString());
    }

    /**
     * 客户端断开连接/异常关闭后自动触发(被动断开)
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("通道断开,有连接断开：{}",ctx.channel().id().toString());
        channelContextUtils.removeContext(ctx.channel());

    }


    /**自定义处理事件：处理握手成功事件（验证token是否失效），并将userId与channel绑定
     * 自动处理WebSocket握手成功事件，即服务端与客户端建立连接成功后触发
     * @param ctx
     * @param evt---触发器对象
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){//如果是握手成功事件，WebSocketServerProtocolHandler.HandshakeComplete是用于判断握手成功事件
            WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
            String url = handshakeComplete.requestUri();//获取url
            String token = getToken(url);
            if(StringTools.isEmpty(token)){//token为空
                return;
            }
            //检查token有效性
            TokenUserInfoDto tokenUserInfoDto = redisComponent.getTokenUserInfoDto(token);
            if(tokenUserInfoDto == null){//token无效
                return;
            }
            logger.info("url:{}", url);
            //绑定userId与channel（这里的channel是服务端与客户端的连接的channel）
            channelContextUtils.addContext(tokenUserInfoDto.getUserId(), ctx.channel());
        }
    }

    /**
     * 获取token
     * @param url 形如：url/ws?token=xxx
     * @return
     */
    private String getToken(String url){
        if(StringTools.isEmpty(url) || !url.contains("?")){//url为空或者url中没有参数token【这里还可以使用url.indexOf("?")==-1 来判断】
            return null;
        }
        String[] urlArr = url.split("\\?");//使用?来分割url得到token=xxx
        if(urlArr.length != 2){
            return null;
        }
        String[] paramArr = urlArr[1].split("=");//使用=来分割token=xxx拿到token具体值
        if(paramArr.length != 2){
            return null;
        }
        return paramArr[1];
    }
}
/*
userEventTriggered：用户事件触发器，用户事件触发器在处理WebSocket握手成功事件时调用
ChannelHandlerContext ctx, Object evt：
ctx---Netty 框架在调用处理器方法时自动传入，包含了当前 ChannelHandler 的上下文信息，每个客户端连上后，当前管道的ctx就不变了
evt---由之前添加到 ChannelPipeline 中的 WebSocketServerProtocolHandler 产生并传播，
一般就是前序处理器创建并传播的对象，通过ctx.fireUserEventTriggered(evt)传递给后序处理器向后续处理器传播事件
 */